/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package evaluation.ged;

import evaluation.Edge;
import evaluation.Graph;
import evaluation.Vertex;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 *
 * @author phong
 */
public class EditPath implements Comparable<EditPath> {

    List<EditOperation> ops;
    Graph Gs, Gd;
    double cost;
    double futureCost = -1;
    
    Set<Vertex> mappedVertices;
    
    public EditPath(Graph Gs, Graph Gd) {
        this.Gs = Gs;
        this.Gd = Gd;
        this.ops = new LinkedList<EditOperation>();
        this.mappedVertices = new LinkedHashSet<Vertex>();
        this.cost = 0;
    }
    
    public double calFutureCost() {
        double c = 0;

        // delete and insert ref/label vertices
        c += Math.abs(Gs.getVertices(Vertex.TYPE_REF).size() - Gd.getVertices(Vertex.TYPE_REF).size());
        c += Math.abs(Gs.getVertices(Vertex.TYPE_LABEL).size() - Gd.getVertices(Vertex.TYPE_LABEL).size());
        
        // delete opt vertices
        Set set = util.Util.toStringSet(Gs.getVertices(Vertex.TYPE_OPT));
        set.removeAll(util.Util.toStringSet(Gd.getVertices(Vertex.TYPE_OPT)));
        c += set.size();

        set = util.Util.toStringSet(Gd.getVertices(Vertex.TYPE_OPT));
        set.removeAll(util.Util.toStringSet(Gs.getVertices(Vertex.TYPE_OPT)));
        c += set.size();

        // delete opt vertices
        set = util.Util.toStringSet(Gs.getVertices(Vertex.TYPE_PRED));
        set.removeAll(util.Util.toStringSet(Gd.getVertices(Vertex.TYPE_PRED)));
        c += set.size();

        set = util.Util.toStringSet(Gd.getVertices(Vertex.TYPE_PRED));
        set.removeAll(util.Util.toStringSet(Gs.getVertices(Vertex.TYPE_PRED)));
        c += set.size();
        

        c += Math.abs(Gs.getEdges().size() - Gd.getEdges().size());
        
        return c;
    }
    
    public boolean successful() {
        return this.Gs.equals(this.Gd);
    }
    
    public double getCost() {
        return this.cost;
    }
    
    public double getFutureCost() {
        if (this.futureCost == -1) 
            this.futureCost = this.calFutureCost();
        return this.futureCost;
    }
    
    public Graph getGs() {
        return Gs;
    }
    
    public Graph getGd() {
        return Gd;
    }
    
    public Set<Vertex> getMappedVertices() {
        return this.mappedVertices;
    }
    
    @Override
    public int compareTo(EditPath that) {
        double this_h = this.getFutureCost();
        double that_h = that.getFutureCost();
        
        if (this.cost + this_h < that.cost + that_h)
            return -1;
        else if (this.cost + this_h  == that.cost + that_h) 
            return 0;
        else 
            return 1;
    }
    
    public EditPath(List<EditOperation> ops, Graph Gs, Graph Gd, Set<Vertex> mapV, double cost) {
        this.ops = ops;
        this.Gs = Gs;
        this.Gd = Gd;
        this.mappedVertices = mapV;
        this.cost = cost;
    }
    
    public EditPath copy() {
        return new EditPath(
                new LinkedList<EditOperation>(this.ops),
                Gs.copy(),
                Gd,
                new LinkedHashSet<Vertex>(this.mappedVertices),
                this.cost);
    }
    
    public void addVertexDeleteion(VertexDeletion del) {
        
        Vertex v = del.getVertex();
        
        // delete all edges relating to this vertex
        for (Edge e : Gs.getEdgesWFstVertex(v)) {
            this.addEdgeDeletion(new EdgeDeletion(e));
        }
        for (Edge e : Gs.getEdgesWSndVertex(v)) {
            this.addEdgeDeletion(new EdgeDeletion(e));
        }
         
        this.ops.add(del);
        this.cost += del.getCost();
        this.Gs.removeVertex(v);
    }
    
    public void addVertexInsertion(VertexInsertion ins) {
        
        Vertex v = ins.getVertex();
        this.ops.add(ins);
        this.cost += ins.getCost();    
        this.Gs.addVertex(v);
        
        this.mappedVertices.add(v);
        
        // add all edges relating to this vertex
        for (Edge e : Gd.getEdgesWFstVertex(v)) {
            if (this.mappedVertices.contains(e.getSecondVertex()))
                this.addEdgeInsertion(new EdgeInsertion(e));
        }
        for (Edge e : Gd.getEdgesWSndVertex(v)) {
            if (this.mappedVertices.contains(e.getFirstVertex()))
                this.addEdgeInsertion(new EdgeInsertion(e));
        }         
    }
    
    public void addVertexSubstitution(VertexSubstitution sub) {
        
        Vertex oldV = sub.getOldVertex();
        Vertex newV = sub.getNewVertex();
        
        List<Edge> outEdges = Gs.getEdgesWFstVertex(oldV);
        List<Edge> inEdges = Gs.getEdgesWSndVertex(oldV);
        
        this.Gs.removeVertex(oldV);
        
        for (Edge es : outEdges) {
            Vertex v2 = es.getSecondVertex();

            if (this.mappedVertices.contains(v2)) {
                
                Edge ed = Gd.getEdges(newV, v2);
                
                if (ed == null) {
                    this.addEdgeDeletion(new EdgeDeletion(es));
                }
                else if (!es.getLabel().equals(ed.getLabel())) {
                    this.addEdgeSubstitution(new EdgeSubstitution(es, ed));
                }
                else {
                    this.Gs.addEdge(ed);
                }
            }
            else {
                this.Gs.addEdge(new Edge(es.getLabel(), newV, v2));
            }
        }

        for (Edge es : inEdges) {
            Vertex v1 = es.getFirstVertex();
            
            if (this.mappedVertices.contains(v1)) {
                
                Edge ed = Gd.getEdges(v1, newV);
                
                if (ed == null) {
                    this.addEdgeDeletion(new EdgeDeletion(es));
                }
                else if (!es.getLabel().equals(ed.getLabel())) {
                    this.addEdgeSubstitution(new EdgeSubstitution(es, ed));
                }
                else {
                    this.Gs.addEdge(ed);
                }
            }
            else {
                this.Gs.addEdge(new Edge(es.getLabel(), v1, newV));                
            }
        }
        
        this.ops.add(sub);
        this.cost += sub.getCost();
        this.Gs.addVertex(newV);

        for (Edge ed : Gd.getEdgesWFstVertex(newV)) {
            Vertex v2 = ed.getSecondVertex();
            if (this.mappedVertices.contains(v2)) {
                Edge es = Gs.getEdges(newV, v2);
                if (es == null) {
                    this.addEdgeInsertion(new EdgeInsertion(ed));
                }
            }
        }

        for (Edge ed : Gd.getEdgesWSndVertex(newV)) {
            Vertex v1 = ed.getFirstVertex();
            if (this.mappedVertices.contains(v1)) {
                Edge es = Gs.getEdges(v1, newV);
                if (es == null) {
                    this.addEdgeInsertion(new EdgeInsertion(ed));
                }
            }
        }

        
        this.mappedVertices.add(newV);        
    }
    
    public void addEdgeDeletion(EdgeDeletion del) {
        this.ops.add(del);
        this.cost += del.getCost();
        this.Gs.removeEdge(del.getEdge());
    }
    
    public void addEdgeInsertion(EdgeInsertion ins) {
        this.ops.add(ins);
        this.cost += ins.getCost();
        this.Gs.addEdge(ins.getEdge());
    }
    
    public void addEdgeSubstitution(EdgeSubstitution sub) {
        this.ops.add(sub);
        this.cost += sub.getCost();
        this.Gs.removeEdge(sub.oldE);
        this.Gs.addEdge(sub.newE);
    }
    
    public void addEditOperation(EditOperation ops) {
        if (ops instanceof VertexDeletion)
            this.addVertexDeleteion((VertexDeletion)ops);
        else if (ops instanceof VertexInsertion)
            this.addVertexInsertion((VertexInsertion)ops);
        else if (ops instanceof VertexSubstitution)
            this.addVertexSubstitution((VertexSubstitution)ops);
        else if (ops instanceof EdgeDeletion)
            this.addEdgeDeletion((EdgeDeletion)ops);
        else if (ops instanceof EdgeInsertion)
            this.addEdgeInsertion((EdgeInsertion)ops);
        else if (ops instanceof EdgeSubstitution)
            this.addEdgeSubstitution((EdgeSubstitution)ops);
    }
    
    @Override
    public String toString() {
        StringBuffer buff = new StringBuffer();
       
        buff.append(this.getCost()).append("\n");
        for (EditOperation op : this.ops) {
            buff.append(op.toString()).append("\n");
        }
       
        return buff.toString();
    }
    
    @Override
    public boolean equals(Object o) {
        if (o instanceof EditPath) {
            EditPath that = (EditPath)o;
            return this.Gs.equals(that.Gs) && that.cost == this.cost && this.mappedVertices.equals(that.mappedVertices);
        }
        return false;
    }
    
    @Override
    public int hashCode() {
        return this.Gs.hashCode() * 31 + this.mappedVertices.hashCode() * 17 + (int)this.cost;
    }    
}